package com.ly.login.web;

import com.ly.common.bean.Result;
import com.ly.common.biz.BizException;
import com.ly.common.util.CheckUtils;
import com.ly.common.util.Utils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("login")
@ConfigurationProperties(prefix = "ly.login")
@Data
public class LoginDo {

    private String userTable = "pub_user";
    private String keyColumn = "id";
    private String nameColumn = "account";
    private String showColumn = "name";
    private String pwdColumn = "pwd";
    private String encryption = "MD5";
    private String sessionUserKey = "entryUser";
    private String userClassName;
    private String cookieUserKey = "KLRGVPNEJLNBFGTGVK";
    private ClassRowMapper crm;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @PostConstruct
    public void init() throws ClassNotFoundException {
        logger.debug(this.toString());
        Class<Object> aClass = (Class<Object>) Class.forName(userClassName);
        crm = new ClassRowMapper(aClass);
    }

    @Resource
    private JdbcTemplate jdbc;

    @ExceptionHandler(BizException.class)
    public Result doBizException(BizException e) {
        return Result.failure(e.getMessage(), e.getErrorCode());
    }

    @RequestMapping("login")
    public Result login(@RequestParam(required = false) String name,
                        @RequestParam(required = false) String pwd,
                        HttpServletResponse response,
                        HttpSession session) throws BizException {
        CheckUtils.checkEmpty(name, "请填写账号!");
        CheckUtils.checkEmpty(pwd, "请填写密码!");
        name = name.trim();
        pwd = pwd.trim();
        // 构建SQL
        String sql = "select * from %s where (%s = ? or id = ?) and %s = ?";
        sql = String.format(sql, userTable, nameColumn, pwdColumn);
        // MD5加密
        if ("MD5".equals(encryption)) {
            pwd = DigestUtils.md5DigestAsHex(pwd.getBytes());
        }
        List<Object> users = jdbc.query(sql, crm, name, name, pwd);
        if (users.isEmpty()) {
            throw new BizException("用户名或密码错误!", null);
        } else if (users.size() > 1) {
            throw new BizException("系统中包含多个同名用户, 请尝试使用用户ID登录!", null);
        } else {
            Object user = users.get(0);
            logger.debug(user.toString());
            // 旧密码, 用于修改密码
            session.setAttribute("oldpwd", Utils.get(user, pwdColumn));
            Utils.set(user, pwdColumn, null);
            session.setAttribute(sessionUserKey, user);
            addUserCookie(Utils.get(user, keyColumn), response);
            return Result.success("登录成功", users.get(0));
        }
    }

    private void addUserCookie(Object uid, HttpServletResponse response) {
        Cookie cookie = new Cookie(cookieUserKey, "" + uid);
        cookie.setMaxAge(uid != null ? 60 * 60 * 24 * 7 : 0);
        response.addCookie(cookie);
    }

    @RequestMapping("reg")
    public Result reg(@RequestParam(required = false) String name,
                      @RequestParam(required = false) String pwd,
                      @RequestParam(required = false) String repwd) throws BizException {
        CheckUtils.checkEmpty(name, "请填写账号!");
        CheckUtils.checkEmpty(pwd, "请填写密码!");
        CheckUtils.checkEmpty(repwd, "请填写确认密码!");
        name = name.trim();
        pwd = pwd.trim();
        repwd = repwd.trim();
        CheckUtils.check(name.length() < 2, "账号不能少于2个字符!");
        CheckUtils.check(pwd.length() < 6, "密码不能少于6个字符!");
        CheckUtils.check(!repwd.equals(pwd), "确认密码与密码不一致!");
        Integer cnt = jdbc.queryForObject(String.format("select count(*) from %s where %s=?", userTable, nameColumn), Integer.class, name);
        CheckUtils.check(cnt > 0, "该账号已经被注册了, 请修改账号!");
        // MD5加密
        if ("MD5".equals(encryption)) {
            pwd = DigestUtils.md5DigestAsHex(pwd.getBytes());
        }
        try {
            if (nameColumn.equals(showColumn)) {
                jdbc.update(String.format("insert into %s (%s,%s) values(?,?)", userTable, nameColumn, pwdColumn), name, pwd);
            } else {
                jdbc.update(String.format("insert into %s (%s,%s,%s) values(?,?,?)", userTable, nameColumn, showColumn, pwdColumn), name, name, pwd);
            }
            return Result.success("注册成功, 您可以使用该账号了!", null);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.failure("注册失败, 请联系管理员!", e.getMessage());
        }
    }

    @RequestMapping("setpwd")
    public Result setpwd(HttpSession session,
                         @SessionAttribute(required = false) String oldpwd,
                         @RequestParam(required = false) String pwd,
                         @RequestParam(required = false) String newpwd,
                         @RequestParam(required = false) String repwd) throws BizException {
        Object entryUser = session.getAttribute(sessionUserKey);
        CheckUtils.checkEmpty(entryUser, "请先登录系统!");
        CheckUtils.checkEmpty(pwd, "请填写原密码!");
        CheckUtils.checkEmpty(newpwd, "请填写新密码!");
        CheckUtils.checkEmpty(repwd, "请填写确认密码!");
        newpwd = newpwd.trim();
        pwd = pwd.trim();
        repwd = repwd.trim();
        CheckUtils.check(newpwd.length() < 6, "新密码不能少于6个字符!");
        CheckUtils.check(!repwd.equals(newpwd), "确认密码与新密码不一致!");
        CheckUtils.check(DigestUtils.md5DigestAsHex(pwd.getBytes()).equals(oldpwd) == false, "原密码错误!");
        CheckUtils.check(DigestUtils.md5DigestAsHex(newpwd.getBytes()).equals(oldpwd), "新密码与原密码一致, 无需修改!");
        // MD5加密
        if ("MD5".equals(encryption)) {
            newpwd = DigestUtils.md5DigestAsHex(newpwd.getBytes());
        }
        try {
            jdbc.update(String.format("update %s set %s = ? where %s = ?", userTable, pwdColumn, keyColumn), newpwd, Utils.get(entryUser, keyColumn));
            return Result.success("密码修改成功!", entryUser);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.failure("密码修改失败, 请联系管理员!", e.getMessage());
        }
    }

    @GetMapping("logout")
    public Result logout(HttpSession session, HttpServletResponse response) {
        session.invalidate();
        addUserCookie(null, response);
        return Result.success("退出成功", null);
    }

    @GetMapping("myinfo")
    public Result myinfo(HttpSession session, HttpServletResponse response, HttpServletRequest request) throws BizException {
        Object entryUser = session.getAttribute(sessionUserKey);
        if (entryUser == null) {
            String id = null;
            if (request != null) {
                Cookie[] cookies = request.getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        if (cookie.getName().equals(cookieUserKey)) {
                            id = cookie.getValue();
                        }
                    }
                }
            }
            if (id != null) {
                String sql = "select * from %s where id = ?";
                sql = String.format(sql, userTable);
                List<Object> users = jdbc.query(sql, crm, id);
                if (users.isEmpty()) {
                    return Result.failure("未登录", null);
                }
                Object user = users.get(0);
                logger.debug(user.toString());
                session.setAttribute("oldpwd", Utils.get(user, pwdColumn));
                Utils.set(user, pwdColumn, null);
                session.setAttribute(sessionUserKey, user);
                if (response != null) {
                    addUserCookie(Utils.get(user, keyColumn), response);
                }
                return Result.success("已登录", user);
            }
            throw new BizException("未登录");
        } else {
            return Result.success("已登录", entryUser);
        }
    }

    @GetMapping("options")
    public LoginDo options() {
        return this;
    }

}

class ClassRowMapper implements RowMapper<Object> {
    Class<Object> aClass;

    public ClassRowMapper(Class<Object> aClass) {
        this.aClass = aClass;
    }

    @Override
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        try {
            Object o = aClass.newInstance();
            for (Field f : aClass.getDeclaredFields()) {
                String cName = Utils.asDbName(f.getName());
                ResultSetMetaData md = rs.getMetaData();
                int i = 0;
                for (; i < md.getColumnCount(); i++) {
                    if (cName.equals(md.getColumnName(i + 1))) {
                        break;
                    }
                }
                // 判断结果集中是否包含该字段
                if (i >= md.getColumnCount()) {
                    continue;
                }
                Object value;
                if (f.getType().isAssignableFrom(String.class)) {
                    value = rs.getString(cName);
                } else if (f.getType().isAssignableFrom(Integer.class) || f.getType().isAssignableFrom(int.class)) {
                    value = rs.getInt(cName);
                } else if (f.getType().isAssignableFrom(Long.class) || f.getType().isAssignableFrom(long.class)) {
                    value = rs.getLong(cName);
                } else if (f.getType().isAssignableFrom(Short.class) || f.getType().isAssignableFrom(short.class)) {
                    value = rs.getShort(cName);
                } else if (f.getType().isAssignableFrom(Byte.class) || f.getType().isAssignableFrom(byte.class)) {
                    value = rs.getByte(cName);
                } else if (f.getType().isAssignableFrom(Float.class) || f.getType().isAssignableFrom(float.class)) {
                    value = rs.getFloat(cName);
                } else if (f.getType().isAssignableFrom(Double.class) || f.getType().isAssignableFrom(double.class)) {
                    value = rs.getDouble(cName);
                } else if (f.getType().isAssignableFrom(Boolean.class) || f.getType().isAssignableFrom(boolean.class)) {
                    value = rs.getBoolean(cName);
                } else if (f.getType().isAssignableFrom(Date.class)) {
                    value = rs.getDate(cName);
                } else if (f.getType().isAssignableFrom(Timestamp.class)) {
                    value = rs.getTimestamp(cName);
                } else if (f.getType().isAssignableFrom(LocalDateTime.class)) {
                    Timestamp t = rs.getTimestamp(cName);
                    value = LocalDateTime.of(t.getYear(), t.getMonth() + 1, t.getDate(), t.getHours(), t.getMinutes(), t.getSeconds());
                } else if (f.getType().isAssignableFrom(LocalDate.class)) {
                    Timestamp t = rs.getTimestamp(cName);
                    value = LocalDate.of(t.getYear(), t.getMonth() + 1, t.getDate());
                } else if (f.getType().isAssignableFrom(LocalTime.class)) {
                    Timestamp t = rs.getTimestamp(cName);
                    value = LocalTime.of(t.getHours(), t.getMinutes(), t.getSeconds());
                } else {
                    value = rs.getObject(cName);
                }
                f.setAccessible(true);
                f.set(o, value);
            }
            return o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
